package controller;

import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;

import javax.media.j3d.*;

import javax.vecmath.*;

import com.sun.j3d.utils.picking.PickResult;

import node.GeoNode;

import math.Spherical;

import controller.camera.Camera;
import controller.camera.CameraListener;
import controller.camera.CameraSetting;

public class CameraController extends AbstractController implements CameraSetting {

	public static final double MIN_ZOOM = 0.1;
	public static final double ZOOM_SPEED = 1;
	public static final double SLOW_ZOOM_SPEED = 0.1;
	public static final double DEFAULT_ZOOM = 4;
	
	private double currentZoomSpeed = ZOOM_SPEED;
	
	private final Point3d focus = new Point3d(0, 0, 0);
	private final Point3d eye = new Point3d();
	
	private int focusMode = FREE;
	
	private final Camera camera;
	
	private final Spherical eyePos = new Spherical();
	private double zoom = DEFAULT_ZOOM;
	
	private int rx, ry, tx, ty;
	private boolean isRotating = false;
	private boolean isTranslating = false;
	
	private Vector3d left=null;
	private Vector3d up=null;
	
	private boolean lockCamera = false;
	
	private CameraListener listener = null;
	
	public CameraController(Canvas3D canvas, BranchGroup bg, Camera camera) {
		super(canvas, bg);
		eyePos.setY(-Math.PI/2.0);
		this.camera = camera;
		updateCamera();
	}
	
	public void setCameraListener(CameraListener listener) {
		this.listener = listener;
	}
	
	public Point3d getCameraPos() {
		return new Point3d(eye);
	}
	
	public Point3d getCameraFocus() {
		return new Point3d(focus);
	}
	
	public void lockCamera() {
		lockCamera = true;
	}
	
	public void unlockCamera() {
		lockCamera = false;
	}
	
	public boolean isLocked() {
		return lockCamera;
	}
	
	private void updateEye() {
		Vector3d eyeVec = eyePos.getVector();
		eyeVec.scale(zoom);
		eye.add(focus, eyeVec);
	}
	
	private void updateCamera() {
		updateEye();
		camera.lookAt(eye, focus);
		if (listener != null) {
			listener.cameraFocusingOn(new Point3d(focus));
		}
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON1) {
			isRotating = true;
			rx = e.getX();
			ry = e.getY();
		}
		if (e.getButton() == MouseEvent.BUTTON3 && focusMode == FREE) {
			isTranslating = true;
//			translationTransform = new Transform3D();
//			translationTransform.lookAt(eye, focus, Camera.DEFAULT_UP);
			
			Vector3d front = new Vector3d(eyePos.getVector());
			front.negate();
			left = new Vector3d();
			up = new Vector3d();
			left.cross(front, Camera.DEFAULT_UP);
			up.cross(left, front);
			left.normalize();
			up.normalize();
			
			tx = e.getX();
			ty = e.getY();
		}
		if (e.getButton() == MouseEvent.BUTTON3 && focusMode == FOCUS_ON_OBJECT) {
			PickResult closest = this.pickClosest(e);
			if (closest != null) {
				Node node = closest.getNode(PickResult.BRANCH_GROUP);
				if (node instanceof GeoNode)
					this.focusOn((GeoNode)node);
			}
		}

	}

	@Override
	public void mouseReleased(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON1) {
			isRotating = false;
		}
		if (e.getButton() == MouseEvent.BUTTON3 && focusMode == FREE) {
			isTranslating = false;
			left = null;
			up = null;
		}

	}

	@Override
	public void mouseDragged(MouseEvent e) {
		if (isLocked()) return;
		if (isRotating) {
			int dx = e.getX() - rx;
			int dy = e.getY() - ry;
			rx = e.getX();
			ry = e.getY();
			
			double yRot = eyePos.getY() + (dx/100.0);
			double zRot = eyePos.getZ() - (dy/100.0);
			if (zRot > Math.PI) zRot = Math.PI - 0.01;
			if (zRot < -Math.PI) zRot = -Math.PI + 0.01;
			eyePos.set(zRot, yRot);
			
			updateCamera();
		} else if (isTranslating && focusMode == FREE) {
			double dx = (e.getX() - tx) * 0.01;
			double dy = -(e.getY() - ty) * 0.01;
			tx = e.getX();
			ty = e.getY();
			
			Vector3d tmp = new Vector3d();
			tmp.set(left);
			tmp.scale(dx);
			focus.add(tmp);
			
			tmp.set(up);
			tmp.scale(dy);
			focus.add(tmp);
			
			updateCamera();
		}

	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
			this.currentZoomSpeed = SLOW_ZOOM_SPEED;
		}

	}

	@Override
	public void keyReleased(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
			this.currentZoomSpeed = ZOOM_SPEED;
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
		if (isLocked()) return;
		if (e.getKeyChar() == 'a' || e.getKeyChar() == 'A') {
			zoom -= this.currentZoomSpeed;
			if (zoom < MIN_ZOOM) zoom = MIN_ZOOM;
			updateCamera();
		} else if (e.getKeyChar() == 'z' || e.getKeyChar() == 'Z') {
			zoom += this.currentZoomSpeed;
			updateCamera();
		}
		
	}
	
	@Override
	public void activate() {
		// TODO Auto-generated method stub

	}

	@Override
	public void deactivate() {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		if (isLocked()) return;
		int clicks = e.getWheelRotation();
		zoom += (clicks*this.currentZoomSpeed);
		if (zoom < MIN_ZOOM) zoom = MIN_ZOOM;
		this.updateCamera();
	}
	
	@Override
	protected boolean canDeselect() {
		return false;
	}

	@Override
	public int getMode() {
		return this.focusMode;
	}

	@Override
	public void setMode(int mode) {
		this.focusMode = mode;
		
	}

	@Override
	public void focusOn(GeoNode node) {
		this.focusMode = FOCUS_ON_OBJECT;
		this.focus.set(node.getPos());
		this.updateCamera();
	}

}
